using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace Ltf.Diffs
{
	public class Diff2Results
	{
		//example diff line - 351,366c351,365
		private const string ChangePattern = "^(?<sfrom>[0-9]+)(,(?<sto>[0-9]+))?(?<type>[c|a|d])(?<tfrom>[0-9]+)(,(?<tto>[0-9]+))?";
		// example diff line - @@ -925,3 +923,0 @@
		private const string UnifiedChangePattern = "@@ -(?<sfrom>[0-9]+)(,(?<slines>[0-9]+))? [+](?<tfrom>[0-9]+)(,(?<tlines>[0-9]+))? @@";
		public List<DiffHunk> Hunks;

		public Diff2Results(List<Nhunker.Nhunk> nhunks)
		{
			Hunks = new List<DiffHunk>();
			for (int i = 1; i < nhunks.Count; i++)
			{
				Nhunker.Nhunk before = nhunks[i - 1];
				Nhunker.Nhunk after = nhunks[i];
				if (before.SourceEnd < after.SourceStart || before.TargetEnd < after.TargetStart)
				{
					DiffHunk hunk = new DiffHunk(before.SourceEnd+1, after.SourceStart, before.TargetEnd+1, after.TargetStart);
					Hunks.Add(hunk);
				}
			}
		}

		public Diff2Results(string diffresults)
		{
			StringReader sr = new StringReader(diffresults);
			Regex changeRegex = new Regex(ChangePattern);

			MatchCollection mc1 = changeRegex.Matches(diffresults);

			Regex unifiedChangeRegex = new Regex(UnifiedChangePattern);

			MatchCollection mc2 = unifiedChangeRegex.Matches(diffresults);

			if (mc1.Count > mc2.Count)
				ParseStandardDiff(sr, changeRegex);
			else
				ParseUnfiedDiff(sr, unifiedChangeRegex);


		}

		public void GetHunkCounts(out int inserts, out int changes, out int deletes)
		{
			inserts = changes = deletes = 0;
			foreach (DiffHunk dh in Hunks)
			{
				switch (dh.HunkType)
				{
					case DiffHunkType.Change:
						changes++;
						break;
					case DiffHunkType.Added:
						inserts++;
						break;
					case DiffHunkType.Deleted:
						deletes++;
						break;
					default:
						break;
				}
			}
		}

		private void ParseUnfiedDiff(StringReader sr, Regex unifiedChangeRegex)
		{
			string line = null;
			DiffHunk change = null;
			Hunks = new List<DiffHunk>();
			do
			{
				line = sr.ReadLine();
				if (line != null)
				{
					Match match = unifiedChangeRegex.Match(line);
					if (match.Success)
					{
						string sFrom = match.Groups["sfrom"].Value;
						string sLines = match.Groups["slines"].Value;
						string tFrom = match.Groups["tfrom"].Value;
						string tLines = match.Groups["tlines"].Value;
						if (sLines=="0")
						{
								change = new DiffHunk(DiffHunkType.Added, new Range(sFrom), Range.RangeFromStringsStartAndInterval(tFrom, tLines));
						}
						else if (tLines=="0")
						{
								change = new DiffHunk(DiffHunkType.Deleted, Range.RangeFromStringsStartAndInterval(sFrom, sLines), new Range(tFrom));
						}
						else
						{
								change = new DiffHunk(DiffHunkType.Change, Range.RangeFromStringsStartAndInterval(sFrom, sLines), Range.RangeFromStringsStartAndInterval(tFrom, tLines));
						}
						Hunks.Add(change);
					}
					else if (change != null && line.StartsWith("-"))
						change.SourceLines.Add(line.Substring(1));
					else if (change != null && line.StartsWith("+"))
						change.TargetLines.Add(line.Substring(1));
				}
			} while (line != null);
		}

		private void ParseStandardDiff(StringReader sr, Regex changeRegex)
		{
			string line = null;
			DiffHunk change = null;
			Hunks = new List<DiffHunk>();
			do
			{
				line = sr.ReadLine();
				if (line != null)
				{
					Match match = changeRegex.Match(line);
					if (match.Success)
					{
						string sFrom = match.Groups["sfrom"].Value;
						string sTo = match.Groups["sto"].Value;
						string tFrom = match.Groups["tfrom"].Value;
						string tTo = match.Groups["tto"].Value;
						switch (match.Groups["type"].Value)
						{
							case "a":
								change = new DiffHunk(DiffHunkType.Added, new Range(sFrom), new Range(tFrom, tTo));
								break;
							case "d":
								change = new DiffHunk(DiffHunkType.Deleted, new Range(sFrom, sTo), new Range(tFrom));
								break;
							case "c":
								change = new DiffHunk(DiffHunkType.Change, new Range(sFrom, sTo), new Range(tFrom, tTo));
								break;
							default:
								break;
						}
						Hunks.Add(change);
					}
					else if (change != null && line.StartsWith("<"))
						change.SourceLines.Add(line.Substring(2));
					else if (change != null && line.StartsWith(">"))
						change.TargetLines.Add(line.Substring(2));
				}
			} while (line != null);
		}

		public enum DiffHunkType
		{
			Change,
			Added,
			Deleted
		}

		public class DiffHunk
		{
			public DiffHunkType HunkType;
			public List<string> TargetLines;
			public List<string> SourceLines;
			public Range TargetRange;
			public Range SourceRange;

			public DiffHunk(DiffHunkType hunkType, Range sourceRange, Range targetRange)
			{
				HunkType = hunkType;
				TargetRange = targetRange;
				SourceRange = sourceRange;
				TargetLines = new List<string>();
				SourceLines = new List<string>();
			}

			public DiffHunk(int sourceStart, int sourceEnd, int targetStart, int targetEnd)
				:this (new Range(sourceStart, sourceEnd),new Range(targetStart, targetEnd)) {	}
			
			public DiffHunk(Range sourceRange, Range targetRange)
			{
				TargetRange = targetRange;
				SourceRange = sourceRange;
				TargetLines = new List<string>();
				SourceLines = new List<string>();
				if (targetRange.Interval < 1)
					HunkType = DiffHunkType.Deleted;
				else if (sourceRange.Interval < 1)
					HunkType = DiffHunkType.Added;
				else
					HunkType = DiffHunkType.Change;
			}

		}



		public struct Range
		{
			public int From;
			public int To;

			public static Range RangeFromStringsStartAndInterval(string from, string interval)
			{
				Range r = new Range(0,0);
				r.From = int.Parse(from);
				if (string.IsNullOrEmpty(interval))
					r.To = r.From;
				else
					r.To = int.Parse(interval)+r.From-1;
				return r;
			}

			public static Range RangeFromStringsStartAndEnd(string from, string to)
			{
				Range r = new Range(0,0);
				r.From = int.Parse(from);
				if (string.IsNullOrEmpty(to))
					r.To = r.From;
				else
					r.To = int.Parse(to);
				return r;
			}

			public Range(string from, string to)
			{
				Range r = new Range(0, 0);
				From = int.Parse(from);
				if (string.IsNullOrEmpty(to))
					To = From;
				else
					To = int.Parse(to);
			}

			public Range(int from, int to)
			{
				From = from;
				To = to;
			}

			public Range(int from)
			{
				From = To = from;
			}
			public Range(string from)
			{
				From = To = int.Parse(from);
			}

			public int Interval
			{
				get { return To - From+1; }
				set { To = From + value-1; }
			}

		}
	}
}
